﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Linq;
using System.Xml.Linq;
using System.Reflection;
using System.Linq.Expressions;
using CoolCode.Reflection;
using CoolCode.Caching;
using CoolCode.Linq;

namespace CoolCode.Data.Xml
{
    public class Repository<T> : IRepository<T>, IXmlTable, IQueryable<T>, IEnumerable<T>, IDisposable where T : class
    {
        private static readonly string entityName = typeof(T).Name;
        private static ICacheStrategy typeCache;
        private static Func<XElement, T, bool> _condition;
        private static Func<T, XElement> _entityToXElement;
        private static bool _initial = false;
        private IDataContext context;
        private XmlQueryable<T> table;

        public XElement Root { get; set; }
        public bool IsChanged { get; set; }
        public string ClassName { get; set; }

        internal Repository(IDataContext context)
        {
            this.context = context;
            this.table = new XmlQueryable<T>((IQueryExecutor)context);
            Initialize();
        }

        private void Initialize()
        {
            if (!_initial)
            {
                typeCache = new HashCacheStrategy();
                ExpressionBuilder exprBuilder = new ExpressionBuilder();
                _condition = exprBuilder.PrimaryKey<T>().Compile();
                _entityToXElement = exprBuilder.ToXElementExpression<T>().Compile();
                _initial = true;
            }
        } 

        #region IRepository<T> Members

        public virtual void Insert(T entity)
        {
            var xe = _entityToXElement(entity); //XmlConvert.Entity2Xml<T>(entity);
            Root.Add(xe);
            IsChanged = true;
        }

        public virtual void Insert(IEnumerable<T> list)
        {
            foreach (T entity in list)
            {
                var xe = XmlConvert.Entity2Xml<T>(entity);
                Root.Add(xe);
            }
            IsChanged = true;
        }

        public virtual void Update(T entity)
        {
            Delete(entity);
            Insert(entity);
            //SaveByPrimaryKeys(entity);
            IsChanged = true;
        }

		public virtual int Update(Expression<Func<T, bool>> predicate, Expression<Func<T, T>> updater)
		{
			throw new NotImplementedException("Linq To Entity not support batch update now!");
		}

        //public virtual void Update(T entity, T original)
        //{
        //    db.Attach(entity, original);
        //}

        public virtual void Update(IEnumerable<T> list)
        {
            foreach (T entity in list)
            {
                var xe = XmlConvert.Entity2Xml<T>(entity);
                Root.Add(xe);
            }
            IsChanged = true;
        }

        public virtual void Delete(T entity)
        {
            DeleteByPrimaryKeys(entity);
            IsChanged = true;
        }

        public virtual void Delete(IQueryable<T> query)
        {
            Delete(query.ToList());
        }

        public virtual void Delete(IEnumerable<T> list)
        {
            //Get PrimaryKeys
            string[] primaryKeys = typeCache.GetItem<T>("ColumnAttribute") as string[];
            if (primaryKeys == null)
            {
                primaryKeys = BLReflection.GetPropFrom<System.Data.Linq.Mapping.ColumnAttribute, T>(c => c.IsPrimaryKey);
                typeCache.AddItem<T>(primaryKeys, "ColumnAttribute");
            }
            int len = primaryKeys.Length;
            string[] pkValue = new string[len];

            //Initial Expression
            // instance  
            ParameterExpression s = Expression.Parameter(typeof(XElement), "s");
            // method                           
            var m = typeof(XElement).GetMethod("Element", new Type[] { typeof(XName) });
            Expression cond = Expression.Constant(false);
            // Expression condTrue  = Expression.Constant(true );
            MemberExpression[] props = new MemberExpression[len];

            for (int i = 0; i < len; i++)
            {
                var mcall = Expression.Call(s, m,
                        new Expression[] { Expression.Constant((XName)primaryKeys[i], typeof(XName)) }             // arguments  
                       );
                props[i] = Expression.Property(mcall, "Value");
            }

            //Join Expression
            foreach (var entity in list)
            {
                Expression subcond = null;
                for (int i = 0; i < len; i++)
                {
                    //Method 1: Get Property and cache it for reuse
                    var p = typeCache.GetItem<T>("Property" + primaryKeys[i]) as PropertyInfo;
                    if (p == null)
                    {
                        Type t = typeof(T);
                        p = t.GetProperty(primaryKeys[i]);
                        typeCache.AddItem<T>(p, "Property" + primaryKeys[i]);
                    }
                    pkValue[i] = XmlConvert.SerializeObject(p.GetValue(entity, null));

                    //Method 2: Get Property directly every time 
                    // pkValue[i] = XmlConvert.SerializeObject(BLReflection.GetPropValue(TEntity, primaryKeys[i]));

                    //Join Condition
                    var value = Expression.Constant(pkValue[i], typeof(string));
                    if (i == 0)
                        subcond = BinaryExpression.Equal(props[i], value);
                    else
                        subcond = BinaryExpression.AndAlso(subcond, BinaryExpression.Equal(props[i], value));
                }
                cond = BinaryExpression.OrElse(cond, subcond);
            }

            // lambda
            var lambda = Expression.Lambda<Func<XElement, bool>>(cond, new ParameterExpression[] { s });

            var xe = Root.Descendants(ClassName).Where(lambda.Compile());
            xe.Remove();

            IsChanged = true;
        }

        public virtual void Delete(Expression<Func<T, bool>> predicate)
        {
            var query = XmlContext.Query<T>(predicate, this.context);
            query.Remove();
            IsChanged = true;
        }

        public virtual IQueryable<T> In<P>(Expression<Func<T, P>> property, params P[] values)
        {
            throw new NotImplementedException();
        }

        public virtual IQueryable<T> Query()
        {
            //return table;
            throw new NotImplementedException();
        }

        public virtual IQueryable<T> Query(Expression<Func<T, bool>> predicate)
        {
            //return table.Where(predicate);
            throw new NotImplementedException();
        }

        //IQueryable<T> IRepository<T>.Where(Expression<Func<T, bool>> predicate)
        //{
        //    //return table.Where(predicate);
        //    throw new NotImplementedException();
        //}

        public virtual T Get(Expression<Func<T, bool>> predicate)
        {
            //return table.FirstOrDefault(predicate); 
            throw new NotImplementedException();
        }


        #endregion

        #region IEnumerable<T> Members

        public IEnumerator<T> GetEnumerator()
        {
            return ((IEnumerable<T>)table).GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion

        #region IQueryable Members

        Type IQueryable.ElementType
        {
            get
            {
                return typeof(T);
            }
        }

        Expression IQueryable.Expression
        {
            get
            {
                return ((IQueryable)table).Expression; // return Expression.Constant(table);
            }
        }

        IQueryProvider IQueryable.Provider
        {
            get { return ((IQueryable)table).Provider; }
        }

        #endregion

        #region IDisposable Members

        public virtual void Dispose()
        {
            if (context != null)
            {
                context.Dispose();
            }
        }

        #endregion

        #region private methods

        private void DeleteByPrimaryKeys(T entity)
        {
            var cond = getConditionDelegate(entity);
            var xe = Root.Descendants(ClassName).Where(cond);
            xe.Remove();
        }

        private void SaveByPrimaryKeys(T entity)
        {
            DeleteByPrimaryKeys(entity);
            var xe = _entityToXElement(entity); //XmlConvert.Entity2Xml<T>(TEntity);
            Root.Add(xe);
        }

        private Func<XElement, bool> getConditionDelegate(T entity)
        {
            return (XElement x) => _condition(x, entity);
        }

        #region Old Methods
        /*
        private Func<XElement, bool> PrimaryKeyExpression(T TEntity)
        {
            string[] primaryKeys = typeCache.GetItem<T>("ColumnAttribute") as string[];
            if (primaryKeys == null)
            {
                primaryKeys = BLReflection.GetPropFrom<System.Data.Linq.Mapping.ColumnAttribute, T>(c => c.IsPrimaryKey);
                typeCache.AddItem<T>(primaryKeys, "ColumnAttribute");
            }
            return PrimaryKeyExpression(TEntity, primaryKeys);
        }

        private Func<XElement, bool> PrimaryKeyExpression(T TEntity, string[] primaryKeys)
        {
            string[] pkValue = new string[primaryKeys.Length];

            for (int i = 0; i < primaryKeys.Length; i++)
            {
                //Method 1: Get Property and cache it for reuse
                var p = typeCache.GetItem<T>("Property" + primaryKeys[i]) as PropertyInfo;
                if (p == null)
                {
                    Type t = typeof(T);
                    p = t.GetProperty(primaryKeys[i]);
                    typeCache.AddItem<T>(p, "Property" + primaryKeys[i]);
                }
                pkValue[i] = XmlConvert.SerializeObject(p.GetValue(TEntity, null));
            }

            ExpressionBuilder builder = new ExpressionBuilder();
            var condExpr = builder.Where(primaryKeys, pkValue);
            var cond = condExpr.Compile();

            return cond;
        }
        */
        #endregion

        #endregion

    }
}
